﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace CppIDE.Core
{

    /// <summary>
    /// Egy projektet reprezentál.
    /// </summary>
    [Serializable]
    public class Project
    {

        /// <summary>
        /// Létrehoz egy új, névtelen projektet.
        /// </summary>
        public Project()
        {
            name = "Névtelen projekt";
            Change += delegate(Object s, EventArgs a) { };
        }

        /// <summary>
        /// Megnyitja a paraméterként kapott projektfájlt, és visszatér a
        /// megnyitott projekttel.
        /// </summary>
        /// <param name="path">a projektfájl elérési útja</param>
        /// <returns>a megnyitott projekt (vagy null, ha nem sikerült)</returns>
        public static Project open(String path)
        {
            Project project;
            FileStream stream = null;
            try
            {
                stream = new FileStream(path, FileMode.Open);
                project = (Project)serializer.Deserialize(stream);
            }
            catch (Exception)
            {
                project = null;
            }
            if (stream != null)
            {
                stream.Close();
            }
            project.Saved = true;
            return project;
        }

        /// <summary>
        /// Ha megváltozik a projekt, ez az esemény fog megtörténni.
        /// </summary>
        public event EventHandler Change;

        /// <summary>
        /// A projekt neve
        /// </summary>
        private String name;

        /// <summary>
        /// A projekt neve tulajdonság
        /// </summary>
        /// <value>A projekt neve</value>
        public String Name
        {
            get
            {
                return name;
            }
            set
            {
                if (name != value)
                {
                    name = value;
                    Saved = false;
                    Change(this, null);
                }
            }
        }

        /// <summary>
        /// A projektfájl
        /// </summary>
        private File file;

        /// <summary>
        /// A projektfájl tulajdonság. Legfeljebb egyszer állítható be.
        /// </summary>
        /// <value>A projektfájl</value>
        public File ProjectFile
        {
            get
            {
                return file;
            }
            set
            {
                if (file != null)
                {
                    throw new Exception("ProjectFile is already set");
                }
                file = value;
            }
        }

        /// <summary>
        /// A projekt könyvtára
        /// </summary>
        private String dirPath;

        /// <summary>
        /// A projekt könyvtára tulajdonság
        /// </summary>
        /// <value>A projekt könyvtára</value>
        public String DirPath
        {
            get
            {
                return dirPath;
            }
            set
            {
                if (dirPath != value)
                {
                    dirPath = value;
                    Saved = false;
                    Change(this, null);
                }
            }
        }

        /// <summary>
        /// Ha igaz, a fájl nem tartalmaz mentetlen módosításokat
        /// </summary>
        private bool saved = false;

        /// <summary>
        /// Ha igaz, a fájl nem tartalmaz mentetlen módosításokat.
        /// Kívülről nem módosítható.
        /// </summary>
        /// <value>Ha igaz, a fájl nem tartalmaz mentetlen módosításokat.</value>
        [XmlIgnore]
        public bool Saved
        {
            get
            {
                return saved;
            }
            private set
            {
                saved = value;
                Change(this, null);
                saved = value;
            }
        }

        /// <summary>
        /// A fordító paraméterei
        /// </summary>
        private String compilerParams = "-Wall";

        /// <summary>
        /// A fordító paraméterei tulajdonság
        /// </summary>
        /// <value>A fordító paraméterei</value>
        public String CompilerParams
        {
            get
            {
                return compilerParams;
            }
            set
            {
                if (compilerParams != value)
                {
                    compilerParams = value;
                    Saved = false;
                    Change(this, null);
                }
            }
        }

        /// <summary>
        /// A linker paraméterei
        /// </summary>
        private String linkerParams = "";

        /// <summary>
        /// A linker paraméterei tulajdonság
        /// </summary>
        /// <value>A linker paraméterei</value>
        public String LinkerParams
        {
            get
            {
                return linkerParams;
            }
            set
            {
                if (linkerParams != value)
                {
                    linkerParams = value;
                    Saved = false;
                    Change(this, null);
                }
            }
        }

        /// <summary>
        /// A projekteben szereplő C++ fájlok.
        /// (Csak azért publikus, hogy szerializálni lehessen.)
        /// </summary>
        public List<CppFile> cpps = new List<CppFile>();

        /// <summary>
        /// A projekteben szereplő header fájlok.
        /// (Csak azért publikus, hogy szerializálni lehessen.)
        /// </summary>
        public List<HeaderFile> headers = new List<HeaderFile>();

        /// <summary>
        /// Visszaadja a projektben szereplő forrásfájlokat (C++ és header).
        /// </summary>
        /// <returns>a fájlok enumerátora</returns>
        public IEnumerator<SourceFile> GetEnumerator()
        {
            foreach (CppFile file in cpps)
            {
                yield return file;
            }
            foreach (HeaderFile file in headers)
            {
                yield return file;
            }
        }
        
        /// <summary>
        /// A projekt kimenete (exe fájl)
        /// </summary>
        private ExecutableFile output;

        /// <summary>
        /// A projekt kimenete tulajdonság (exe fájl)
        /// </summary>
        /// <value>A projekt kimenete (exe fájl)</value>
        public ExecutableFile Output
        {
            get
            {
                return output;
            }
            set
            {
                bool changed = (output == null && value != null);
                changed = changed || (output != null && !output.Equals(value));
                if (changed)
                {
                    output = value;
                    Saved = true;
                    Change(this, null);
                }
            }
        }

        /// <summary>
        /// A projekt felépítése (teljes fordítása, exe készítése)
        /// </summary>
        /// <param name="success">igaz, ha sikerült</param>
        /// <returns>Error-ok listája (lehet, hogy üres)</returns>
        public List<Error> build(out bool success)
        {
            List<Error> errors = new List<Error>();
            success = true;
            foreach (CppFile file in cpps)
            {
                bool fileSuccess;
                foreach (Error error in file.compile(compilerParams, out fileSuccess))
                {
                    errors.Add(error);
                }
                success = success && fileSuccess;
            }
            if (success)
            {
                errors.AddRange(link(out success));
            }
            return errors;
        }

        /// <summary>
        /// A projekt összelinkelése
        /// </summary>
        /// <param name="success">igaz, ha sikerült</param>
        /// <returns>Error-ok listája (lehet, hogy üres)</returns>
        private List<Error> link(out bool success)
        {
            List<Error> errors = new List<Error>();
            if (!isLinkingNeeded())
            {
                success = true;
                return errors;
            }
            System.Diagnostics.Trace.WriteLine("Linking...");
            Application application = Application.getInstance();
            String gccPath = application.Config.GccPath;
            String gppBin = gccPath + "bin\\g++.exe";
            ProcessStartInfo startInfo = new ProcessStartInfo(gppBin);
            startInfo.Arguments = linkerParams;
            foreach (CppFile cppFile in cpps)
            {
                startInfo.Arguments += " \"" + cppFile.Output.FullPath + "\"";
            }
            startInfo.Arguments += " -o \"" + Output.FullPath + "\"";
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardError = true;
            Process process = Process.Start(startInfo);
            String stdError = process.StandardError.ReadToEnd();

            String[] lines = stdError.Split('\n');
            foreach (String line in lines)
            {
                String message = line.Trim();
                if (message.Length == 0) continue;
                errors.Add(new Error(message));
            }
            success = process.ExitCode == 0;
            return errors;
        }

        /// <summary>
        /// Ha igaz, akkor szükség van linkelésre (lásd: inkrementális fordítás)
        /// </summary>
        /// <returns>Szükség van-e linkelésre?</returns>
        private bool isLinkingNeeded()
        {
            if (!Output.Exist)
            {
                return true;
            }
            DateTime outputDate = Output.LastModification;
            foreach (CppFile file in cpps)
            {
                if (!file.Exist)
                {
                    return true;
                }
                int compareResult = outputDate.CompareTo(file.LastModification);
                if (compareResult < 0)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// A projekt kimenetének futtatása
        /// </summary>
        /// <returns>sikeresség</returns>
        public bool execute()
        {
            if (Output == null)
            {
                return false;
            }
            else
            {
                return Output.execute();
            }
        }

        /// <summary>
        /// A projekt takarítása, azaz az összes kimeneti fájl törlése
        /// (exe és objekt fájlok).
        /// </summary>
        public void clean()
        {
            foreach (CppFile file in cpps)
            {
                file.Output.Delete();
            }
            if (Output != null)
            {
                Output.Delete();
            }
        }

        /// <summary>
        /// Egy forrásfájl hozzáadása a projekthez.
        /// </summary>
        /// <param name="file">a forrásfájl</param>
        public void addFile(SourceFile file)
        {
            if (file is CppFile)
            {
                CppFile cppFile = file as CppFile;
                if (!cpps.Contains(cppFile))
                {
                    cpps.Add(cppFile);
                    Saved = false;
                    Change(this, null);
                }
            }
            else
            {
                HeaderFile headerFile = file as HeaderFile;
                if (!headers.Contains(headerFile))
                {
                    headers.Add(headerFile);
                    Saved = false;
                    Change(this, null);
                }
            }
        }

        /// <summary>
        /// Egy forrásfájl eltávolítása a projektből.
        /// </summary>
        /// <param name="file">az eltávolítandó forrásfájl</param>
        public void removeFile(SourceFile file)
        {
            bool removed = false;
            if (file is CppFile)
            {
                removed = cpps.Remove(file as CppFile);
            }
            else if (file is HeaderFile)
            {
                removed = headers.Remove(file as HeaderFile);
            }
            if (removed)
            {
                Saved = false;
                Change(this, null);
            }
        }

        /// <summary>
        /// Ha igaz, akkor a paraméterben megadott forrásfájl része a projektnek.
        /// </summary>
        /// <param name="file">A kérdéses forrásfájl</param>
        /// <returns>Része-e a forrásfájl a projektnek?</returns>
        public bool contains(SourceFile file)
        {
            if (file is CppFile)
            {
                return cpps.Contains(file as CppFile);
            }
            else if (file is HeaderFile)
            {
                return headers.Contains(file as HeaderFile);
            }
            return false;
        }

        /// <summary>
        /// A szerializációhoz használt serializer
        /// </summary>
        static protected XmlSerializer serializer = new XmlSerializer(typeof(Project));

        /// <summary>
        /// Menti a projektet.
        /// </summary>
        public void save()
        {
            FileStream stream = new FileStream(ProjectFile.FullPath, FileMode.Create);
            serializer.Serialize(stream, this);
            stream.Close();
            Saved = true;
        }

    }

}
